from channels.generic.websocket import WebsocketConsumer,AsyncWebsocketConsumer
from channels.exceptions import StopConsumer
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from django.core.cache import caches
import time
from datetime import datetime,timedelta
import json
from .logger import info_logger,error_logger
import traceback

WS_CACHE = caches['redis']
#给channel_name对应的连接发送数据
def send_to_channel_name(channel_name,data,func='forward.message'):
    '''
    :param channel_name: 连接的channel_name
    :param data: 要发送的数据
    :param func: 有消费者类中哪个方法来处理这个数据，方法名自定义，加上是forward_message,传递的应该是formard.message
    :return:
    '''
    # 给指定的channel_name发送消息
    try:
        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.send)(
            channel_name,  # 通道名
            {
                'type': func, # 转发给channel_name对应的消费者类下的，func（.转成_）方法来处理这个消息
                'text': data if isinstance(data,str) else json.dumps(data,ensure_ascii=False), #转发给消费者的数据
            }
        )
        return True
    except Exception as e:
        return str(e)

#channel_layer通过channel_name主动关闭客户端连接
def server_close_client_by_channel_name(channel_name,log_msg=None):
    try:
        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.send)(
            channel_name,
            {
                "type":"channel.disconnect",
                'log_msg':log_msg #在转发方法中记录日志
            }
        )
        return True
    except Exception as e:
        return str(e)

#异常处理的装饰器
def handle_consumer_exceptions(func):
    def wrapper(self, *args, **kwargs):
        try:
            return func(self, *args, **kwargs)
        except Exception as e:
            # 在装饰器中操作消费者实例 self
            # 这里可以执行你希望的自行处理的操作
            error = {
                'type':'error',
                'msg':f'服务器内部报错了，请联系管理员'
            }
            #报错内容，栈内
            error_traceback = traceback.format_exc()
            self.send(json.dumps(error,ensure_ascii=False))
            error_logger(f"[websocket报错]\n{error_traceback}")
    return wrapper

###1、点对点通信：删除cache数据
class UserToUserConsumer(WebsocketConsumer):
    KEY_LINK = '=*<user-to-user>*=' #缓存到redis时，拼接上的字符串
    Cache = WS_CACHE #缓存到的cahce后端
    Datetime_Format = '%Y-%m-%d %H:%M:%S'
    TIMEOUT=None #缓存到cache的过期，默认是不过期
    EXP = 1 #连接设置的过期时间，分钟
    Logger = info_logger #日志

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        #缓存channel_name和过期时间到redis
        self.save_data = {"exp_time":None,"channel_name":None}
        #返回的错误提示的格式
        self.error_data ={"type":"error",'msg':None}
        #删除cache缓存的channel_name数据：存在相同

    def delete_channel_name_cache(self,cache_key=None):
        '''
        cache_key:
           不为None： 在自己的连接中，触发接收用户的断开连接时

        功能：当close关闭掉连接时，将cache中缓存的channel_name数据删除掉
        1、连接存在时，拒绝新连接创建时，千万不能删除cache中数据
        2、用户主动断开连接、用户的连接过期时，都应该删除cache数据，以释放资源
        :return:
        '''
        #删除channel_name和过期时间
        if isinstance(cache_key,str):
            self.Cache.delete(cache_key)
            self.Logger(f'url={self.scope["path"]},用户={cache_key.split(self.KEY_LINK)[0]},触发了删除cache操作了')
        else:
            self.Cache.delete(self.cache_key)
            self.Logger(f'url={self.scope["path"]},用户={self.username},触发了删除cache操作了')


    def save_channel_name_cache(self,set_before=False):
        '''
            set_before:
                False: 将channel_name的过期时间=当前时间+1分钟
                True: 将channel_name的过期时间=当前时间-1分钟  （在客户端主动断开时，要将其设置已经过期了）
            功能：保存当前channel_name和过期时间到cache中
        :return:
        '''
        if set_before:
            self.save_data['exp_time'] = (datetime.now() - timedelta(minutes=self.EXP)).strftime(self.Datetime_Format)
        else:
            self.save_data['exp_time'] = (datetime.now() + timedelta(minutes=self.EXP)).strftime(self.Datetime_Format)
        self.save_data['channel_name'] = self.channel_name
        #缓存到cache中
        self.Cache.set(self.cache_key, self.save_data, timeout=self.TIMEOUT)

    def connect(self):
        # 当前连接所属的用户
        self.username = self.scope['url_route']['kwargs']['username']
        # 日志的前部分消息
        self.log_msg = f'路由={self.scope["path"]},当前用户={self.username},日志内容：'
        # 当前用户的cache_key
        self.cache_key = '{}{}'.format(self.username, self.KEY_LINK)
        # 关闭连接时，code对应的日志消息
        self.code_log = {
            1000: self.log_msg+'是客户端主动断开连接,[code=1000]',
            4006: self.log_msg+f'用户{self.username}已经存在了连接，无法创建新的连接，[code=4006,在方法connect调用close(4006)]',
            3000: self.log_msg+f'用户{self.username}太久没有更新心跳，在发送消息时，被服务端断开连接了,[code=3000,在方法receive调用close(3000)]',
            4000: '用户在转发消息时，发现接收的用户连接已经过期，通过channel_name将其断开，[code=4000,在方法receive使用channel_layer][在channel_disconnect调用close(4000)]',
        }

        #缓存在redis中：channel_name, 过期时间（当前时间+1分钟，在心跳处理时，重新设置过期时间）
        cache_dic = self.Cache.get(self.cache_key)
        #当前时间
        now_time = datetime.now().strftime(self.Datetime_Format)
        if cache_dic:
            exp_time = cache_dic.get('exp_time') #连接设置的过期时间
            channel_name = cache_dic.get('channel_name') #channel_name
            if now_time>exp_time:
                #已经过期了,该用户可以创建新的连接了
                ##1、服务端通过channel_name断开之前连接
                self.error_data['msg'] = f'{self.EXP}分钟未更新心跳，现断开您的连接了'
                # 断开“用户”的连接前，告知断开的原因
                send_to_channel_name(channel_name,json.dumps(self.error_data,ensure_ascii=False))
                #调用断开连接的方法
                log_msg = self.log_msg + f'\n用户={self.username}开启新连接时，发现之前存在的连接已经过期，对前连接进行关闭'
                server_close_client_by_channel_name(channel_name,log_msg=log_msg)
                ##2、允许客户连接
                self.accept()
                ##3、缓存当前用户设置的过期时间和channel_name,覆盖前连接的
                self.save_channel_name_cache()
                #日志记录
                log_msg = self.log_msg+f'\n用户{self.username} 创建连接成功,(之前的连接过期)'
                self.Logger(log_msg)##
            else:
                ###存在的连接未过期：不能创建新的连接,先允许连接（但不更新cache内容），发送提示消息，再断开连接
                #允许连接：但不更新cache
                # self.close(1006)
                self.accept()
                #发送提示消息
                self.error_data['msg']=f'{self.username}已经存在连接了，将自动关闭您的连接'
                self.send(json.dumps(self.error_data,ensure_ascii=False))
                #断开连接：accept之后close,code=3000-4999，accept之前close，code=1006
                self.close(4006)
                #不能删除cache中的数据，连接正常运行中了
        else:
            ###redis中没有缓存当前用户的channel_name
            ##1、允许客户连接
            self.accept()
            ##2、缓存当前用户设置的过期时间和channel_name
            self.save_channel_name_cache()
            #日志记录
            log_msg = self.log_msg + f'\n用户{self.username} 创建连接成功,(cache中没有数据)'
            self.Logger(log_msg)
    @handle_consumer_exceptions
    def receive(self, text_data=None, bytes_data=None):
        ###1、判断当前用户的连接是否过期，（更新过期时间，只能在心跳处理中更新）
        cache_data = self.Cache.get(self.cache_key)
        if cache_data:
            exp_time = cache_data.get('exp_time')
            now_time = datetime.now().strftime(self.Datetime_Format)
            if now_time>exp_time:
                self.error_data['msg']=f'{self.EXP}分钟未更新心跳，现断开您的连接了'
                # 断开连接前，发送数据给客户端告知原因
                self.send(json.dumps(self.error_data,ensure_ascii=False))
                # 断开当前客户端连接
                self.close(3000)
                # 删除channel_name和过期时间
                self.delete_channel_name_cache()
                return
        else:
            ## 连接正常运行，手动操作redis数据库删除cache_key对应的数据，就会触发这个
            log_msg = self.log_msg +f'\n用户{self.username},连接正常运行时，cache中缓存的channel_name数据被删除掉了[可能是手动删除cache后端存储中的数据了]'
            self.Logger(log_msg)
            #在redis中手动删除掉channel_name后，缓存当前连接的channel_name和过期时间
            self.save_channel_name_cache()

        ###2、心跳维持
        if text_data in ('ping','PING'):
            #接收的心跳时，重新缓存channel_name和过期时间
            self.save_channel_name_cache()
            #返回心跳消息
            self.send('PONG')
            # 日志记录
            log_msg =self.log_msg+f'用户{self.username}的心跳'
            self.Logger(log_msg)
            return
        else:
            try:
                dic = json.loads(text_data)
                if not isinstance(dic,dict):
                    self.error_data['msg'] = '请发送{"to":"用户","type":"","data":{}}格式的数据'
                    self.send(json.dumps(self.error_data,ensure_ascii=False))
                    # 日志记录
                    log_msg = self.log_msg+f'\n用户{self.username}发送的数据格式有问题\n格式不能是={text_data}'
                    self.Logger(log_msg)
                    return
            except Exception:
                self.error_data['msg'] = '请发送{"to":"用户","type":"","data":{}}格式的数据'
                self.send(json.dumps(self.error_data,ensure_ascii=False))
                # 日志记录
                log_msg = self.log_msg + f'\n用户{self.username}发送的数据格式有问题\n格式不能是={text_data}'
                self.Logger(log_msg)
                return

        ###3、转发的数据处理
        msg_type = dic.get('type')
        if msg_type == 'send':
            #转发给哪个用户
            to_username = dic.get('to')
            #转发给该用户的数据
            to_data = dic.get('data')
            #获取要转给的用户的的channel_name
            to_cache_key = '{}{}'.format(to_username,self.KEY_LINK)
            #获取接收用户的channel_name和exp_time
            to_cache_data = self.Cache.get(to_cache_key)
            #存在该接收用户，就转发
            if to_cache_data:
                now_time = datetime.now().strftime(self.Datetime_Format)
                exp_time = to_cache_data.get('exp_time')
                to_channel_name = to_cache_data.get('channel_name')
                if now_time>exp_time:
                    ##1、接收的用户，连接已经过期,close(4000)，告知断开、记录日志、断开、删除cache数据
                    self.error_data['msg'] = '太久没有更新心跳，现断开您的连接了'
                    # 断开“接收用户”的连接前，告知断开的原因
                    send_to_channel_name(to_channel_name, json.dumps(self.error_data, ensure_ascii=False))
                    # 断开“接收用户”的连接：触发channel_disconnect方法
                    log_msg = self.log_msg+f'\n{self.username}转发数据给{to_username}时，用户{to_username}连接已经过期，通过channel_name将其断开，[code=4000,在方法receive,转发给用户]'
                    server_close_client_by_channel_name(to_channel_name,log_msg=log_msg)
                    # 删除接收用户的channel_name和过期时间，code=4000
                    self.delete_channel_name_cache(to_cache_key)

                    ##2、告知发送消息的用户，消息发送失败
                    self.error_data['msg'] = '{}不在线，无法发送'.format(to_username)
                    self.send(json.dumps(self.error_data,ensure_ascii=False))
                else:
                    ##2、开始转发数据
                    # 转发数据
                    send_data = {"type":"recv","from":self.cache_key.split(self.KEY_LINK)[0],"data":to_data}
                    # 调用转发
                    is_send = send_to_channel_name(to_channel_name,json.dumps(send_data,ensure_ascii=False))
                    # 日志记录
                    log_msg = self.log_msg + f'\n用户{self.username}转发给{to_username}成功\n转发数据：{text_data}'
                    if isinstance(is_send,str):
                        log_msg = self.log_msg+f'\n用户{self.username}转发给{to_username}失败\n转发数据：{text_data}\n报错：{is_send}'
                    self.Logger(log_msg)

            ###4、接收用户不存在，无法转发消息
            else:
                self.error_data['msg'] = '{}不在线，无法发送'.format(to_username)
                self.send(json.dumps(self.error_data,ensure_ascii=False))
                # 日志记录
                log_msg = self.log_msg + f'\n用户{self.username}转发给{to_username}失败，{to_username}用户不在线\n转发数据：{text_data}'
                self.Logger(log_msg)
        elif msg_type == 'test':
            self.Logger('进入这里了')
            #如果报错，会中断连接的
            raise Exception('报错了')

    @handle_consumer_exceptions
    def forward_message(self,*args):
        '''
        自定义方法，用户之间实现数据转发，连接对象外部调用此方法给此连接发送消息
        :param args:
        (
            {
                'type': 'forward.message', #别的连接通过另一个连接的channel_name调用的方法
                'text': '{"type": "recv", "from": "lhz", "data": {"msg": "good!"}}' #传递的消息
                'log_msg':'日志内容' #记录的日志内容
            },

        )
        :return:
        '''
        data = args[0]
        text_data = data.get('text','')#传递进来的数据
        #在对象外，channel_layer通过channel_name 给对应的websocket发送消息
        json_data = text_data if isinstance(text_data,str) else json.dumps(text_data,ensure_ascii=False)
        self.send(json_data)

    # 对象外部，使用channel_layer关闭连接
    def channel_disconnect(self, *args):
        '''
        消费者对象外部通过channel_name,关闭此客户端连接
        :param args:({'type': 'channel.disconnect', 'log_msg': '日志消息'},)
        :return:
        '''
        data = args[0]
        log_msg = data.get('log_msg', '')
        # 1、在对象外，给channel_layer使用,通过channel_name 来关闭连接
        # 2、close会触发disconnect方法
        # 记录日志
        if log_msg:
            log_msg += ' [在channel_disconnect调用close(4000)]'
            self.code_log[4000] = log_msg
        ##调用close会触发，调开对象的连接
        self.close(4000)



    #对象自己调用
    def disconnect(self, code):
        '''
        连接断开时，会触发该方法，在这里主要记录日志消息
        :param code:
            1000=客户端主动断开
            1006=在connect方法中调用close(1006)
            3000=在channel_disconnect方法中调用close(3000)
            3001=在receive方法中，当前连接发送消息时，发现当前连接过期，调用close(3001)
        :return:
        '''
        # 1、在connect方法中，没有调用accept时，就会调用
        # 2、在对象中，调用close方法，就会调用该方法断开当前连接
        ##删除当前连接的缓存的channel_name数

        log_msg = self.code_log.get(code,f'服务端主动关闭{self.username}的连接')
        if code == 1000:
            # 客户端主动关闭连接
            self.Logger(log_msg)
            #删除channel_name和过期时间
            self.delete_channel_name_cache()
        elif code in[3000,4000,4006]:
            #可控的主动触发关闭客户端连接
            self.Logger(log_msg)
        else:
            #非可控的断开客户端连接
            log_msg+=' 其他情况导致服务端中断客户端连接了'
            self.Logger(log_msg)

        ###2、群聊广播通信：一个用户只能创建一个连接


###1、点对点通信：不删除cache数据
class UserToUserConsumerV2(WebsocketConsumer):
    KEY_LINK = '=*<user-to-user>*=' #缓存到redis时，拼接上的字符串
    Cache = WS_CACHE #缓存到的cahce后端
    Datetime_Format = '%Y-%m-%d %H:%M:%S'
    TIMEOUT=None #缓存到cache的过期，默认是不过期
    EXP = 1 #连接设置的过期时间，分钟
    Logger = info_logger #日志

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        #缓存channel_name和过期时间到redis
        self.save_data = {"exp_time":None,"channel_name":None}
        #返回的错误提示的格式
        self.error_data ={"type":"error",'msg':None}
        #删除cache缓存的channel_name数据：存在相同

    def save_channel_name_cache(self,set_before=False):
        '''
            set_before:
                False: 将channel_name的过期时间=当前时间+1分钟
                True: 将channel_name的过期时间=当前时间-1分钟  （在客户端主动断开时close(1000)，要将其设置已经过期了）
            功能：保存当前channel_name和过期时间到cache中
        :return:
        '''
        if set_before:
            #把channel_name设置成已经过期
            self.save_data['exp_time'] = (datetime.now() - timedelta(minutes=self.EXP)).strftime(self.Datetime_Format)
        else:
            #把channel_name的过期时间往后加1分钟
            self.save_data['exp_time'] = (datetime.now() + timedelta(minutes=self.EXP)).strftime(self.Datetime_Format)
        self.save_data['channel_name'] = self.channel_name
        #缓存到cache中
        self.Cache.set(self.cache_key, self.save_data, timeout=self.TIMEOUT)

    def connect(self):
        # 当前连接所属的用户
        self.username = self.scope['url_route']['kwargs']['username']
        # 日志的前部分消息
        self.log_msg = f'路由={self.scope["path"]},当前用户={self.username},日志内容：'
        # 当前用户的cache_key
        self.cache_key = '{}{}'.format(self.username, self.KEY_LINK)
        # 关闭连接时，code对应的日志消息
        self.code_log = {
            1000: self.log_msg+'是客户端主动断开连接,[code=1000]',
            4006: self.log_msg+f'用户{self.username}已经存在了连接，无法创建新的连接，[code=4006,在方法connect调用close(4006)]',
            3000: self.log_msg+f'用户{self.username}太久没有更新心跳，在发送消息时，被服务端断开连接了,[code=3000,在方法receive调用close(3000)]',
            4000: '用户在转发消息时，发现接收的用户连接已经过期，通过channel_name将其断开，[code=4000,在方法receive使用channel_layer][在channel_disconnect调用close(4000)]',
        }

        #缓存在redis中：channel_name, 过期时间（当前时间+1分钟，在心跳处理时，重新设置过期时间）
        cache_dic = self.Cache.get(self.cache_key)
        #当前时间
        now_time = datetime.now().strftime(self.Datetime_Format)
        if cache_dic:
            exp_time = cache_dic.get('exp_time') #连接设置的过期时间
            channel_name = cache_dic.get('channel_name') #channel_name
            if now_time>exp_time:
                #已经过期了,该用户可以创建新的连接了
                ##1、服务端通过channel_name断开之前连接
                self.error_data['msg'] = f'{self.EXP}分钟未更新心跳，现断开您的连接了'
                # 断开“用户”的连接前，告知断开的原因
                send_to_channel_name(channel_name,json.dumps(self.error_data,ensure_ascii=False))
                #调用断开连接的方法
                log_msg = self.log_msg + f'用户={self.username}开启新连接时，发现之前存在的连接已经过期，对前连接进行关闭'
                server_close_client_by_channel_name(channel_name,log_msg=log_msg)
                ##2、允许客户连接
                self.accept()
                ##3、缓存当前用户设置的过期时间和channel_name,覆盖前连接的
                self.save_channel_name_cache()
                #日志记录
                log_msg = self.log_msg+f'用户{self.username} 创建连接成功,(之前的连接过期)'
                self.Logger(log_msg)##
            else:
                ###存在的连接未过期：不能创建新的连接,先允许连接（但不更新cache内容），发送提示消息，再断开连接
                #允许连接：但不更新cache
                # self.close(1006)
                self.accept()
                #发送提示消息
                self.error_data['msg']=f'{self.username}已经存在连接了，将自动关闭您的连接'
                self.send(json.dumps(self.error_data,ensure_ascii=False))
                #断开连接：accept之后close,code=3000-4999，accept之前close，code=1006
                self.close(4006)
                #不能删除cache中的数据，连接正常运行中了
        else:
            ###redis中没有缓存当前用户的channel_name
            ##1、允许客户连接
            self.accept()
            ##2、缓存当前用户设置的过期时间和channel_name
            self.save_channel_name_cache()
            #日志记录
            log_msg = self.log_msg + f'用户{self.username} 创建连接成功,(cache中没有数据)'
            self.Logger(log_msg)
    @handle_consumer_exceptions
    def receive(self, text_data=None, bytes_data=None):
        ###1、判断当前用户的连接是否过期，（更新过期时间，只能在心跳处理中更新）
        cache_data = self.Cache.get(self.cache_key)
        if cache_data:
            exp_time = cache_data.get('exp_time')
            now_time = datetime.now().strftime(self.Datetime_Format)
            if now_time>exp_time:
                self.error_data['msg']=f'{self.EXP}分钟未更新心跳，现断开您的连接了'
                # 断开连接前，发送数据给客户端告知原因
                self.send(json.dumps(self.error_data,ensure_ascii=False))
                # 断开当前客户端连接
                self.close(3000)
                return
        else:
            ## 连接正常运行，手动操作redis数据库删除cache_key对应的数据，就会触发这个
            log_msg = self.log_msg +f'用户{self.username},连接正常运行时，cache中缓存的channel_name数据被删除掉了[可能是手动删除cache后端存储中的数据了]'
            self.Logger(log_msg)
            #在redis中手动删除掉channel_name后，缓存当前连接的channel_name和过期时间
            self.save_channel_name_cache()

        ###2、心跳维持
        if text_data in ('ping','PING'):
            #接收的心跳时，重新缓存channel_name和过期时间
            self.save_channel_name_cache()
            #返回心跳消息
            self.send('PONG')
            # 日志记录
            log_msg =self.log_msg+f'用户{self.username}的心跳'
            self.Logger(log_msg)
            return
        else:
            try:
                dic = json.loads(text_data)
                if not isinstance(dic,dict):
                    self.error_data['msg'] = '请发送{"to":"用户","type":"","data":{}}格式的数据'
                    self.send(json.dumps(self.error_data,ensure_ascii=False))
                    # 日志记录
                    log_msg = self.log_msg+f'用户{self.username}发送的数据格式有问题\n格式不能是={text_data}'
                    self.Logger(log_msg)
                    return
            except Exception:
                self.error_data['msg'] = '请发送{"to":"用户","type":"","data":{}}格式的数据'
                self.send(json.dumps(self.error_data,ensure_ascii=False))
                # 日志记录
                log_msg = self.log_msg + f'用户{self.username}发送的数据格式有问题\n格式不能是={text_data}'
                self.Logger(log_msg)
                return

        ###3、转发的数据处理
        msg_type = dic.get('type')
        if msg_type == 'send':
            #转发给哪个用户
            to_username = dic.get('to')
            #转发给该用户的数据
            to_data = dic.get('data')
            #获取要转给的用户的的channel_name
            to_cache_key = '{}{}'.format(to_username,self.KEY_LINK)
            #获取接收用户的channel_name和exp_time
            to_cache_data = self.Cache.get(to_cache_key)
            #存在该接收用户，就转发
            if to_cache_data:
                now_time = datetime.now().strftime(self.Datetime_Format)
                exp_time = to_cache_data.get('exp_time')
                to_channel_name = to_cache_data.get('channel_name')
                if now_time>exp_time:
                    ##1、接收的用户，连接已经过期,close(4000)，告知断开、记录日志、断开、删除cache数据
                    self.error_data['msg'] = '太久没有更新心跳，现断开您的连接了'
                    # 断开“接收用户”的连接前，告知断开的原因
                    send_to_channel_name(to_channel_name, json.dumps(self.error_data, ensure_ascii=False))
                    # 断开“接收用户”的连接：触发channel_disconnect方法
                    log_msg = self.log_msg+f'{self.username}转发数据给{to_username}时，用户{to_username}连接已经过期，通过channel_name将其断开，[code=4000,在方法receive,转发给用户]'
                    server_close_client_by_channel_name(to_channel_name,log_msg=log_msg)

                    ##2、告知发送消息的用户，消息发送失败
                    self.error_data['msg'] = '{}不在线，无法发送'.format(to_username)
                    self.send(json.dumps(self.error_data,ensure_ascii=False))
                    log_msg = self.log_msg+f'{self.username}转发给{to_username}失败,[cache缓存的时间已经过期]\n转发的数据{text_data}'
                    self.Logger(log_msg)

                else:
                    ##2、开始转发数据
                    # 转发数据
                    send_data = {"type":"recv","from":self.cache_key.split(self.KEY_LINK)[0],"data":to_data}
                    # 调用转发
                    is_send = send_to_channel_name(to_channel_name,json.dumps(send_data,ensure_ascii=False))
                    # 日志记录
                    log_msg = self.log_msg + f'用户{self.username}转发给{to_username}成功\n转发数据：{text_data}'
                    if isinstance(is_send,str):
                        log_msg = self.log_msg+f'用户{self.username}转发给{to_username}失败\n转发数据：{text_data}\n报错：{is_send}'
                    self.Logger(log_msg)

            ###4、接收用户不存在，无法转发消息
            else:
                self.error_data['msg'] = '{}不在线，无法发送'.format(to_username)
                self.send(json.dumps(self.error_data,ensure_ascii=False))
                # 日志记录
                log_msg = self.log_msg + f'用户{self.username}转发给{to_username}失败，用户{to_username}不在线，[cache中无数据]\n转发数据：{text_data}'
                self.Logger(log_msg)
        elif msg_type == 'test':
            self.Logger('进入这里了')
            #如果报错，会中断连接的
            raise Exception('报错了')

    @handle_consumer_exceptions
    def forward_message(self,*args):
        '''
        自定义方法，用户之间实现数据转发，连接对象外部调用此方法给此连接发送消息
        :param args:
        (
            {
                'type': 'forward.message', #别的连接通过另一个连接的channel_name调用的方法
                'text': '{"type": "recv", "from": "lhz", "data": {"msg": "good!"}}' #传递的消息
                'log_msg':'日志内容' #记录的日志内容
            },

        )
        :return:
        '''
        data = args[0]
        text_data = data.get('text','')#传递进来的数据
        #在对象外，channel_layer通过channel_name 给对应的websocket发送消息
        json_data = text_data if isinstance(text_data,str) else json.dumps(text_data,ensure_ascii=False)
        self.send(json_data)

    # 对象外部，使用channel_layer关闭连接
    def channel_disconnect(self, *args):
        '''
        消费者对象外部通过channel_name,关闭此客户端连接
        :param args:({'type': 'channel.disconnect', 'log_msg': '日志消息'},)
        :return:
        '''
        data = args[0]
        log_msg = data.get('log_msg', '')
        # 1、在对象外，给channel_layer使用,通过channel_name 来关闭连接
        # 2、close会触发disconnect方法
        # 记录日志
        if log_msg:
            log_msg += ' [在channel_disconnect调用close(4000)]'
            self.code_log[4000] = log_msg
        ##调用close会触发，调开对象的连接
        self.close(4000)



    #对象自己调用
    def disconnect(self, code):
        '''
        连接断开时，会触发该方法，在这里主要记录日志消息
        :param code:
            1000=客户端主动断开
            1006=在connect方法中调用close(1006)
            3000=在channel_disconnect方法中调用close(3000)
            3001=在receive方法中，当前连接发送消息时，发现当前连接过期，调用close(3001)
        :return:
        '''
        # 1、在connect方法中，没有调用accept时，就会调用
        # 2、在对象中，调用close方法，就会调用该方法断开当前连接
        ##删除当前连接的缓存的channel_name数
        log_msg = self.code_log.get(code,f'服务端主动关闭{self.username}的连接')
        if code == 1000:
            # 客户端主动关闭连接
            self.Logger(log_msg)
            #将cache的过期时间，设置成已经过期了
            self.save_channel_name_cache(set_before=True)
        elif code in[3000,4000,4006]:
            #可控的主动触发关闭客户端连接，这里的过期时间都已经过期了
            self.Logger(log_msg)
        else:
            #非可控的断开客户端连接
            log_msg=f'路由={self.scope["path"]}，用户={self.username},code={code},未知情况导致服务端中断客户端连接了'
            self.Logger(log_msg)
            # 将cache的过期时间，设置成已经过期了
            self.save_channel_name_cache(set_before=True)



        ###2、群聊广播通信：一个用户只能创建一个连接
